/*
 ============================================================================
 Name        : tpPPD.c
 Author      : superacidos
 Version     :
 Copyright   : Your copyright notice
 Description : Hello World in C, Ansi-style
 ============================================================================
 */


#include <stdio.h>
#include <stdlib.h>
#include "operacionesIO.h"
#include "config.h"
#include "consola.h"
#include "log.h"

extern FILE *volumenFile = NULL;

void SincroOriginal(chs datosconfig,
					stPedido *pedidioRecibido,
					pthread_mutex_t *semaforoContador,
					stNodoPedido **listaPedidos,
					stNodoPedido **listaEjecutar,
					stNodoSincro **listaSincro,
					char ** algoritmo,
					int32_t *agregadosEje,
					int32_t *contadorEje,
					pthread_mutex_t *semaforoGeneral);
void SincroPares(chs datosconfig,
					stPedido *pedidioRecibido,
					pthread_mutex_t *semaforoContador,
					stNodoPedido **listaPedidos,
					stNodoPedido **listaEjecutar,
					stNodoSincro **listaSincro,
					char ** algoritmo,
					int32_t *agregadosEje,
					int32_t *contadorEje,
					pthread_mutex_t *semaforoGeneral);

void SincroImpares(chs datosconfig,
					stPedido *pedidioRecibido,
					pthread_mutex_t *semaforoContador,
					stNodoPedido **listaPedidos,
					stNodoPedido **listaEjecutar,
					stNodoSincro **listaSincro,
					char ** algoritmo,
					int32_t *agregadosEje,
					int32_t *contadorEje,
					pthread_mutex_t *semaforoGeneral);

int32_t main(void)
{

	stNodoPedido* listaPedidos,*listaEjecutar;
	stNodoSincro* listaSincro;
	char *pathFatFile = (char *)malloc(150);
	char *configConsola = (char *)malloc(200);
	char *pathConsola = (char *)malloc(150);
	char *modoInicio = (char *)malloc(8);
	char *dirIp = (char *)malloc(16);
	char *dirIpLocal = (char *)malloc(16);
	char *puertoLocal = (char *)malloc(5);
	char *puerto = (char *)malloc(5);
	char *idDisco = (char *)malloc(10);
	char *c = (char *)malloc(10);
	char *h = (char *)malloc(10);
	char *s = (char *)malloc(10);
	char *algoritmo = (char *)malloc(10);
	char *delayLectura = (char *)malloc(8);
	char *delayEscritura = (char *)malloc(8);
	char *rpm =(char *)malloc(8);
	char *tiempoSaltoPistasMs =(char *)malloc(4);
	char *hayQLoguear =(char *)malloc(10);
	char *conversion ;
	int32_t total=0;
	int32_t cont=0;
	int32_t agregadosEjecutar, contadorEjecutar;
	agregadosEjecutar = 0;
	contadorEjecutar= 10;
	sem_t semaforoContador;

	loadConfig(pathFatFile,modoInicio,idDisco,dirIp,puerto,c,h,s,
				algoritmo,dirIpLocal,puertoLocal,delayLectura,
				delayEscritura,rpm,tiempoSaltoPistasMs,hayQLoguear,
				pathConsola,configConsola);


    int32_t intHayQueLoguear = atoi(hayQLoguear);
    free(hayQLoguear);

    stPedido *pedido;
	pthread_mutex_t semaforo;
	pthread_t thread,threadConsola;


	int32_t fd = open(pathFatFile, O_RDWR);

	if( fd == -1 ){
		perror("");
		return 0;
	}

	fdatasync(fd);
	posix_fadvise(fd, 0,0,POSIX_FADV_RANDOM);

	volumenFile = fdopen(fd,"r+b");


	chs *posicionActual = (chs *)malloc(sizeof(chs));
	chs datosConfig;
	posicionActual->c = 0;
	posicionActual->h = 1;
	posicionActual->s = 0;


	/*prueba prints de numeros
	 * posicionActual->s = 203;
	printf("d: %d \n",posicionActual->s);
	printf("i: %i \n",posicionActual->s);
	printf("o: %o \n",posicionActual->s);
	printf("u: %u \n",posicionActual->s);*/

	datosConfig.c = atoi(c);
	datosConfig.h = atoi(h);
	datosConfig.s = atoi(s);
	datosConfig.seekTime = obtenerSeekTime(atof(rpm),datosConfig.s);

	free(rpm);

	int32_t IntdelayLectura;
	int32_t IntdelayEscritura;
	IntdelayEscritura = atoi(delayEscritura);
	IntdelayLectura= atoi(delayLectura);

	free(delayEscritura);
	free(delayLectura);
	free(c);
	free(h);
	free(s);

	int32_t pid =getpid();

    int32_t sd,nbytes;
    sd=0;
    PAQUETE_NIPC_CABECERA *cabecera = (PAQUETE_NIPC_CABECERA*)malloc(sizeof(PAQUETE_NIPC_CABECERA));

	if(strncmp("CONNECT",modoInicio,7) ==0)
	{
		//mandamos el handshake al raid
		sd = ConectarRaid(dirIp,puerto);

		strcat(idDisco,"\0");
		int32_t payloadlength = strlen(idDisco)+1;
		char *paq = serializarNipc(HANDSHAKE,idDisco,payloadlength);

		send(sd,paq,payloadlength + sizeof(PAQUETE_NIPC_CABECERA),0);

		free(paq);
		//hasta aca handshake

		while (1)
		{
			nbytes = recv(sd, cabecera, sizeof(PAQUETE_NIPC_CABECERA), MSG_WAITALL);
			if (nbytes <= 0)
			{
				//error ver descripciones
				if(nbytes == 0)
				{
					printf("dessconexion sideral \n");

				}else{
					printf("chau raid un amigo \n");
				}

				return 0;
			}
			else
			{

				switch(cabecera->type)
				{
					case (uint8_t)HANDSHAKE:

						listaPedidos = (stNodoPedido *)malloc(sizeof(stNodoPedido));
						listaPedidos->info = NULL;
						listaPedidos->sgte = NULL;

						listaEjecutar = (stNodoPedido *)malloc(sizeof(stNodoPedido));
						listaEjecutar->info = NULL;
						listaEjecutar->sgte = NULL;

						listaSincro = (stNodoSincro *)malloc(sizeof(stNodoSincro));
						listaSincro->sector = NULL;
						listaSincro->sgte = NULL;

						//lanzo el hilo que atiende pedidos
						stDatosHilo *args = (stDatosHilo *)malloc(sizeof(stDatosHilo));
						args->pathFatFile = &pathFatFile;
						args->pathConsola = &pathConsola;
						args->algoritmo = &algoritmo;
						args->configConsola = &configConsola;

						args->datosConfig = datosConfig;
						args->listaPedidos = &listaPedidos;
						args->listaEjecutar = &listaEjecutar;
						args->posicionActual = &posicionActual;
						args->delayEscritura = IntdelayEscritura;
						args->delayLectura = IntdelayLectura;
						args->listaSincro = &listaSincro;
						args->hayQueLoguear = intHayQueLoguear;

						args->agregadosEjecutar = agregadosEjecutar;
						args->contadorEjecutar = contadorEjecutar;
						args->sentidoGiro = 1;
						args->contSincro = 0;

						args->socketRaid = sd;
						if(cabecera->payloadlength == 0)
						{
							args->estoySincronizado = 1;
						}
						else
						{
							args->estoySincronizado = 0;
						}

						printf("Conectado!\n");

						sem_init(&semaforoContador, 0, 0);
						pthread_mutex_init(&semaforo,NULL);

						args->semaforoContador = &semaforoContador;
						args->semaforo = &semaforo;

						pthread_create(&thread,NULL,(void*)&atenderPedidos,args);

						pthread_create(&threadConsola,NULL,(void*)&atenderConsola,args);

						break;
					case (uint8_t)SINCROESCRIBIR:
					case (uint8_t)SINCROLEER:


						pedido = recibirDeSocket(sd,cabecera,&total);
					if(total<sizeof(stPedido))
					{
						printf("sector: %ld tipoop: %u bytesrecv: %d \n",pedido->sector,pedido->tipoOperacion,total);
					}
						pthread_mutex_lock(&semaforo);
						AgregarPedido(&listaPedidos,pedido,&algoritmo,
								&agregadosEjecutar,
								&listaEjecutar,&listaSincro,&contadorEjecutar,
								&semaforoContador);
						pthread_mutex_unlock(&semaforo);

						/*AgregarPedido(&listaPedidos,pedido,&algoritmo,
								&agregadosEjecutar,&listaEjecutar,
								&listaSincro,&contadorEjecutar,
								&semaforoContador);*/


						break;

					case (uint8_t)SINCROLEERTODOS:
						pedido = recibirDeSocket(sd,cabecera,&total);

						SincroOriginal(datosConfig,pedido,&semaforoContador,
								&listaPedidos,&listaEjecutar,
								&listaSincro,&algoritmo,&agregadosEjecutar,
								&contadorEjecutar,&semaforo);
						break;
					case (uint8_t)SINCROLEERIMPARES:
						pedido = recibirDeSocket(sd,cabecera,&total);

						SincroImpares(datosConfig,pedido,&semaforoContador,
								&listaPedidos,&listaEjecutar,
								&listaSincro,&algoritmo,&agregadosEjecutar,
								&contadorEjecutar,&semaforo);
						break;
					case (uint8_t)SINCROLEERPARES:
						pedido = recibirDeSocket(sd,cabecera,&total);

						SincroPares(datosConfig,pedido,&semaforoContador,
								&listaPedidos,&listaEjecutar,
								&listaSincro,&algoritmo,&agregadosEjecutar,
								&contadorEjecutar,&semaforo);
						break;

					case (uint8_t)LEER:
					case (uint8_t)ESCRIBIR:

						pedido = recibirDeSocket(sd,cabecera,&total);

						char *msj = malloc(100);
						if (cabecera->type == LEER)
						{
							sprintf(msj,"Leer sector %ld",pedido->sector);
						}
						else
						{
							sprintf(msj,"Escribir sector %ld",pedido->sector);
						}
						logMessage("LLEGA OPERACION",pid,0,0,msj,intHayQueLoguear);
						free(msj);

						//printf("agregar pedido \n");
						pthread_mutex_lock(&semaforo);
						AgregarPedido(&listaPedidos,pedido,&algoritmo,agregadosEjecutar,&listaEjecutar,&listaSincro,contadorEjecutar,&semaforoContador);
						pthread_mutex_unlock(&semaforo);


						break;
					case (uint8_t)TAMANIODISCO:
						//calculo y devuelvo el tamaño del disco en base a datosConfig
						conversion = (char *)malloc(100);
						sprintf(conversion,"%u",(datosConfig.c*datosConfig.s));
						strcat(conversion,"\0");
						payloadlength = strlen(conversion)+1;
						paq = serializarNipc(TAMANIODISCO,conversion,payloadlength);
						send(sd,paq,sizeof(PAQUETE_NIPC_CABECERA)+payloadlength,0);

						free(conversion);
						free(paq);
						break;

					default:
						printf("hoy no");
						break;

				}
			}


		}


	}
	else
	{

	   fd_set master;   // conjunto maestro de descriptores de fichero
		fd_set read_fds; // conjunto temporal de descriptores de fichero para select()
		struct sockaddr_in myaddr;     // dirección del servidor
		struct sockaddr_in remoteaddr; // dirección del cliente
		int32_t fdmax;        // número máximo de descriptores de fichero
		int32_t listener;     // descriptor de socket a la escucha
		int32_t newfd;        // descriptor de socket de nueva conexión aceptada
		int32_t nbytes;
		int32_t yes=1;        // para setsockopt() SO_REUSEADDR, más abajo
		int32_t addrlen;
		int32_t i;
		struct timeval timeout;
		timeout.tv_sec = 0;
		timeout.tv_usec = 1000;

		FD_ZERO(&master);    // borra los conjuntos maestro y temporal
		FD_ZERO(&read_fds);




		// obtener socket a la escucha
		if ((listener = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
			perror("socket");
			exit(1);
		}

		// obviar el mensaje "address already in use" (la dirección ya se está usando)
		if (setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &yes,
															sizeof(int)) == -1) {
			perror("setsockopt");
			exit(1);
		}

		// enlazar
		myaddr.sin_family = AF_INET;
		myaddr.sin_addr.s_addr = inet_addr(dirIpLocal);
		myaddr.sin_port = htons(atoi(puertoLocal));
		memset(&(myaddr.sin_zero), '\0', 8);
		if (bind(listener, (struct sockaddr *)&myaddr, sizeof(myaddr)) == -1) {
			perror("bind");
			exit(1);
		}

		// escuchar
		if (listen(listener, 10) == -1) {
			perror("listen");
			exit(1);
		}

		// añadir listener al conjunto maestro
		FD_SET(listener, &master);

		// seguir la pista del descriptor de fichero mayor
		fdmax = listener; // por ahora es éste

		PAQUETE_NIPC_CABECERA *cabecera;

		printf("se encendio el proceso PPD en la ip: %s puerto: %s \n",dirIp,puerto);

		// bucle principal
		for(;;) {
			read_fds = master; // cópialo
			if (select(fdmax+1, &read_fds, NULL, NULL, &timeout) == -1) {
				perror("select");
				exit(1);
			}

			// explorar conexiones existentes en busca de datos que leer
			for(i = 0; i <= fdmax; i++) {
				if (FD_ISSET(i, &read_fds)) { // ¡¡tenemos datos!!
					if (i == listener) {
						// gestionar nuevas conexiones
						addrlen = sizeof(remoteaddr);
						if ((newfd = accept(listener, (struct sockaddr *)&remoteaddr,&addrlen)) == -1) {
							perror("accept");
						} else {
							FD_SET(newfd, &master); // añadir al conjunto maestro
							if (newfd > fdmax) {    // actualizar el máximo
								fdmax = newfd;
							}
							printf("selectserver: new connection from %s on "
								"socket %d \n", inet_ntoa(remoteaddr.sin_addr), newfd);
						}
					} else {
						cabecera = (PAQUETE_NIPC_CABECERA*)malloc(sizeof(PAQUETE_NIPC_CABECERA));
						// gestionar datos de un cliente
						if ((nbytes = recv(i, cabecera, sizeof(PAQUETE_NIPC_CABECERA), MSG_WAITALL)) <= 0) {
							// error o conexión cerrada por el cliente
							if (nbytes == 0) {
								// conexión cerrada
								printf("selectserver: socket %d hung up \n", i);
								//TODO: SE CERRO LA CONEXION CON EL SOCKET VER SI
								//BUSCAMOS POR SOCKET EL DISCO Y DECIMOS QUE ESTA CAIDO
								//ES ACA CUANDO TENEMOS QUE REDISTRIBUIR LAS LECTURAS QUE TIENE ASIGNADAS
								//ENTRE EL RESTO DE LOS PPD


							} else {
								perror("recv");
							}
							close(i); // ¡Hasta luego!
							FD_CLR(i, &master); // eliminar del conjunto maestro
						} else {
							// tenemos datos de algún cliente
							//ACA VA NUESTRA LOGICA EN BASE A LO QUE RECIBAMOS DEL PROTOCOLO NIPC

							switch(cabecera->type)
							{
								case (uint8_t)HANDSHAKE:
									if(cont == 0)
									{//ACAAAAAAAA
										listaPedidos = (stNodoPedido *)malloc(sizeof(stNodoPedido));
										listaPedidos->info = NULL;
										listaPedidos->sgte = NULL;

										listaEjecutar = (stNodoPedido *)malloc(sizeof(stNodoPedido));
										listaEjecutar->info = NULL;
										listaEjecutar->sgte = NULL;


										//lanzo el hilo que atiende pedidos
										stDatosHilo *args = (stDatosHilo *)malloc(sizeof(stDatosHilo));
										args->pathFatFile = &pathFatFile;
										args->algoritmo = &algoritmo;
										args->pathConsola = &pathConsola;
										args->configConsola = &configConsola;

										args->datosConfig = datosConfig;
										args->listaPedidos = &listaPedidos;
										args->listaEjecutar = &listaEjecutar;
										args->posicionActual = &posicionActual;
										args->delayEscritura = IntdelayEscritura;
										args->delayLectura = IntdelayLectura;

										args->agregadosEjecutar = agregadosEjecutar;
										args->contadorEjecutar = contadorEjecutar;
										args->sentidoGiro = 1;
										args->estoySincronizado = 0;
										args->hayQueLoguear = intHayQueLoguear;

										sem_init(&semaforoContador, 0, 0);
										pthread_mutex_init(&semaforo,NULL);

										args->semaforoContador = &semaforoContador;
										args->semaforo = &semaforo;
										pthread_create(&thread,NULL,(void*)&atenderPedidos,args);
										pthread_create(&threadConsola,NULL,(void*)&atenderConsola,args);

									}


									cont++;


									char *respuesta =serializarNipcSinPayload(PAYOK,0);
									send(i,respuesta,sizeof(PAQUETE_NIPC_CABECERA),0);
									free(respuesta);

									break;
								case (uint8_t)LEER:
								case (uint8_t)ESCRIBIR:

									pedido = recibirDeSocket(i,cabecera,&total);

									char *msj = malloc(100);
									if (cabecera->type == LEER)
									{
										sprintf(msj,"Leer sector %ld",pedido->sector);
									}
									else
									{
										sprintf(msj,"Escribir sector %ld",pedido->sector);
									}
									logMessage("LLEGA OPERACION",pid,0,0,msj,intHayQueLoguear);
									free(msj);

									pedido->socketPFS = i;
									pthread_mutex_lock(&semaforo);
									AgregarPedido(&listaPedidos,pedido,&algoritmo,
												&agregadosEjecutar,&listaEjecutar,
												&listaSincro,&contadorEjecutar,
												&semaforoContador);

									/*AgregarPedido(&listaPedidos,pedido,&algoritmo,
											agregadosEjecutar,&listaEjecutar,
											&listaSincro,contadorEjecutar,
											&semaforoContador);*/
									pthread_mutex_unlock(&semaforo);



									break;
								default:
									printf("hoy no\n");
									break;

							}

						}
					} // Esto es ¡TAN FEO!
				}
			}
		}
	}



	return 1;
}

void SincroOriginal(chs datosconfig,
					stPedido *pedidioRecibido,
					pthread_mutex_t *semaforoContador,
					stNodoPedido **listaPedidos,
					stNodoPedido **listaEjecutar,
					stNodoSincro **listaSincro,
					char ** algoritmo,
					int32_t *agregadosEje,
					int32_t *contadorEje,
					pthread_mutex_t *semaforoGeneral)
{

	long cantSectores = datosconfig.c* datosconfig.s;
	long i = 0;
	for(;i<cantSectores;i++)
	{
		stPedido *pedido = (stPedido*)malloc(sizeof(stPedido));
		pedido->sector = i;
		pedido->socketPFS = pedidioRecibido->socketPFS;
		pedido->tipoOperacion = SINCROLEER;

		//printf("bloquea 2\n");
		pthread_mutex_lock(semaforoGeneral);
		AgregarPedido(listaPedidos,pedido,algoritmo,
				agregadosEje, listaEjecutar,listaSincro,
				contadorEje,semaforoContador);
		pthread_mutex_unlock(semaforoGeneral);
		//printf("DESbloquea 2\n");
	}

}

void SincroPares(chs datosconfig,
					stPedido *pedidioRecibido,
					pthread_mutex_t *semaforoContador,
					stNodoPedido **listaPedidos,
					stNodoPedido **listaEjecutar,
					stNodoSincro **listaSincro,
					char ** algoritmo,
					int32_t *agregadosEje,
					int32_t *contadorEje,
					pthread_mutex_t *semaforoGeneral)
{

	long cantSectores = datosconfig.c* datosconfig.s;
	long i = 0;
	for(;i<cantSectores;i++)
	{
		if (i%2 == 0)
		{
			stPedido *pedido = (stPedido*)malloc(sizeof(stPedido));
			pedido->sector = i;
			pedido->socketPFS = pedidioRecibido->socketPFS;
			pedido->tipoOperacion = SINCROLEER;

			//printf("bloquea 2\n");
			pthread_mutex_lock(semaforoGeneral);
			AgregarPedido(listaPedidos,pedido,algoritmo,
					agregadosEje, listaEjecutar,listaSincro,
					contadorEje,semaforoContador);
			pthread_mutex_unlock(semaforoGeneral);
		}//printf("DESbloquea 2\n");
	}

}

void SincroImpares(chs datosconfig,
					stPedido *pedidioRecibido,
					pthread_mutex_t *semaforoContador,
					stNodoPedido **listaPedidos,
					stNodoPedido **listaEjecutar,
					stNodoSincro **listaSincro,
					char ** algoritmo,
					int32_t *agregadosEje,
					int32_t *contadorEje,
					pthread_mutex_t *semaforoGeneral)
{

	long cantSectores = datosconfig.c* datosconfig.s;
	long i = 1;
	for(;i<cantSectores;i++)
	{
		if (i%2 != 0)
		{
			stPedido *pedido = (stPedido*)malloc(sizeof(stPedido));
			pedido->sector = i;
			pedido->socketPFS = pedidioRecibido->socketPFS;
			pedido->tipoOperacion = SINCROLEER;

			//printf("bloquea 2\n");
			pthread_mutex_lock(semaforoGeneral);
			AgregarPedido(listaPedidos,pedido,algoritmo,
					agregadosEje, listaEjecutar,listaSincro,
					contadorEje,semaforoContador);
			pthread_mutex_unlock(semaforoGeneral);
		}//printf("DESbloquea 2\n");
	}

}
